LeetCode Longest Increasing Subsequence 题解。
原题
大意就是在一个未排序的数组中找出最长递增子序列的长度。
Constraints
- 未排序的int数组
- 数组长度未说明,可能为0
- 递增子序列:子序列就是从原数组中去除掉任意个元素(任意位置)所剩下的元素组成的序列(也就是说子序列中元素原先不必是相邻的),递增表示子序列后面的数比前面的大
- 返回最长子序列的长度,如果数组为0就返回0
Ideas
第一个想法
先创建一个数组dp,dp[i]表示以数组中第i个元素结尾的最长递增子序列的长度。
然后依次遍历数组中的每个元素,对于每一个遍历到的元素A,检查前面的所有子序列,看能不能将A添加到子序列末尾,如果能,这样就形成了一个更长的子序列。在所有能以A为结尾的子序列中找到最长的那个,然后更新dp数组对应的长度。
最后,dp数组中最大值就是所要的结果。
对于这个算法,每次遍历到一个数都要检查前面遍历过的数,时间复杂度是$O(n^2)$,因为要使用到dp数组,空间复杂度也为$O(n)$。
第二个想法
先创建一个数组dp,dp[i]表示长度为i的递增子序列的最后一个数的最小值。
然后依次遍历数组中的每个元素,在遍历到第i个元素时,查看dp数组中下标小于i的元素,如果有数比第i个元素大,就替换这个元素来保证dp[i]表示长度为i的递增子序列的最后一个数的最小值,如果有相等的就什么也不错,如果都比第i个元素小,那么将第i个元素值赋给dp[i]。
最后,dp数组中最后一个有值的元素的下标就是所要的结果。
对于这个算法,每次遍历到一个数都要检查前面遍历过的数,时间复杂度是$O(n^2)$,因为要使用到dp数组,空间复杂度也为$O(n)$。
第三个想法
可以发现在第二个算法中的dp数组应该是一个递增数组。因为每次遍历到第i个数,都只在该数比dp[0..i-1]的数大才将这个数设置给dp[i],所以该数组是递增的。
那么对于这个递增的数组我们可以使用二分查找来提升性能。
这时时间复杂度是$O(nlog(n))$,因为要使用到dp数组,空间复杂度也为$O(n)$。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
| import java.util.Arrays;
public class LongestIncreasingSubsequence300 { public int lengthOfLIS(int[] nums) { if (nums == null || nums.length == 0) return 0; int dp[] = new int[nums.length]; for (int i = 0; i < dp.length; i++) { dp[i] = 1; } int res = dp[0]; for (int i = 1; i < nums.length; i++) { int max = dp[i]; for (int j = 0; j < i; j++) { if (nums[j] < nums[i]) { int tmp = dp[j] + 1; max = max < tmp ? tmp : max; } } dp[i] = max; if (dp[i] > res) { res = dp[i]; } } return res; }
算法二 dp[i]表示长度为i的递增子序列的最后一个数的可能的最小值 越小 那么就有更大的可能后面的的数比这个大 从而使整个递增子序列 更长
dp 数组是递增的 可以用反证法证明 */ public int lengthOfLIS2(int[] nums) { if (nums == null || nums.length == 0) return 0; int dp[] = new int[nums.length + 1]; dp[0] = Integer.MIN_VALUE; int res = 0; for (int i = 0; i < nums.length; i++) { int j = 0; for (; j < res; j++) { if (dp[j] == nums[i]) { break; } else if (dp[j] > nums[i]) { dp[j] = nums[i]; break; } } if (j == res) { dp[res++] = nums[i]; } } return res; }
算法三 对2的改进 因为dp是递增的,所以可以将线性遍历改为二分查找 */ public int lengthOfLIS3(int[] nums) { if (nums == null || nums.length == 0) return 0; int dp[] = new int[nums.length + 1]; dp[0] = Integer.MIN_VALUE; int res = 0; for (int i = 0; i < nums.length; i++) { int p = Arrays.binarySearch(dp, 0, res + 1, nums[i]); if (p < 0) { p = - (p + 1); dp[p] = nums[i]; res = p > res ? p : res; } } return res; } }
|
Test
LeetCode测试通过。